from datetime import datetime
from collections import Counter
import random
from sqlalchemy import desc
from app import db, mongo
from app.models import UserTags, Tags, MovieTags, Movies

# Initialize MongoDB client
recommend_col = mongo.cx['movies']['recommend_result']
score_col = mongo.cx['movies']['score_record']

def recommend_movies(user_id: int, refresh: bool = False) -> list[int]:
    """
    Generate exactly 3 movie recommendations for a given user.
    - If refresh=False and record exists, return existing.
    - If refresh=True, force re-generate and ensure different from last.
    Results are stored in MongoDB recommend_result collection.
    Returns a list of 3 movie IDs.
    """
    existing = recommend_col.find_one({'user_id': user_id})
    if existing and not refresh:
        return existing['movie_id']

    # Step 1: Fetch up to 3 user-interested tags from MySQL
    user_tags = (
        db.session.query(UserTags)
        .filter(UserTags.user_id == user_id)
        .limit(3)
        .all()
    )
    interested_tag_ids = [ut.tag_id for ut in user_tags]

    # Step 2: Fetch user's high ratings (>=8) from MongoDB
    user_scores = list(score_col.find({'user_id': user_id, 'user_rating': {'$gte': 8}}).sort('user_rating', -1))

    movie_ids = [s['movie_id'] for s in user_scores]
    recommendations = []

    # Helper to find top movie by tag, excluding already recommended or rated
    def top_movie_by_tag(tag_id, exclude_ids):
        sub_q = (
            db.session.query(Movies.id, Movies.rate)
            .join(MovieTags, MovieTags.movie_id == Movies.id)
            .filter(MovieTags.tag_id == tag_id, ~Movies.id.in_(exclude_ids))
            .order_by(desc(Movies.rate))
            .first()
        )
        return sub_q.id if sub_q else None

    # Build top_tags list
    top_tags = []
    if user_scores:
        movie_tags = (
            db.session.query(MovieTags.tag_id)
            .filter(MovieTags.movie_id.in_(movie_ids))
            .all()
        )
        all_tags = [mt.tag_id for mt in movie_tags]
        tag_counts = Counter(all_tags)
        top_tags = [tag for tag, _ in tag_counts.most_common(3)]
        if len(top_tags) < 3:
            for t in interested_tag_ids:
                if t not in top_tags:
                    top_tags.append(t)
                if len(top_tags) == 3:
                    break
    else:
        top_tags = interested_tag_ids[:3]

    def generate_recommendations(exclude_ids=None):
        if exclude_ids is None:
            exclude_ids = []
        result = []
        for tag_id in top_tags:
            mid = top_movie_by_tag(tag_id, result + movie_ids + exclude_ids)
            if mid:
                result.append(mid)
        if len(result) < 3:
            needed = 3 - len(result)
            extra = (
                db.session.query(Movies.id)
                .filter(~Movies.id.in_(result + movie_ids if user_scores else result))
                .order_by(desc(Movies.rate))
                .limit(needed)
                .all()
            )
            result.extend([e.id for e in extra])
        return result[:3]

    # If refreshing, ensure new recommendation differs from previous
    max_retry = 5
    if refresh and existing:
        old_set = set(existing['movie_id'])
        for _ in range(max_retry):
            random.shuffle(top_tags)
            recs = generate_recommendations(exclude_ids=list(old_set))
            if set(recs) != old_set:
                recommendations = recs
                break
        else:
            recommendations = generate_recommendations()  # fallback even if same
    else:
        recommendations = generate_recommendations()

    # Store to MongoDB
    recommend_doc = {
        'user_id': user_id,
        'movie_id': recommendations,
        'created_at': datetime.now()
    }
    recommend_col.replace_one({'user_id': user_id}, recommend_doc, upsert=True)

    return recommendations
